package com.hmall.trade.config;

import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * 自定义ID生成器：生成不超过JS安全整数(2^53 - 1)的Long，避免前端精度丢失。
 * 结构（低位 -> 高位）：
 * sequence(7 bits) + workerId(5 bits) + timeDelta(41 bits) = 53 bits
 * 最高值 < 2^53。
 */
@Slf4j
@Component
public class SafeJsIdentifierGenerator implements IdentifierGenerator {
    // JS 最大安全整数
    private static final long JS_SAFE_MAX = 9007199254740991L; // 2^53 - 1
    // 起始时间戳（自定义纪元）: 2024-01-01 00:00:00 GMT+8 大约 1704038400000L，但取UTC标准：2024-01-01 UTC
    private static final long EPOCH = 1704067200000L; // 2024-01-01 00:00:00 UTC

    private static final long WORKER_ID_BITS = 5L;          // 5 bits workerId
    private static final long SEQUENCE_BITS = 7L;           // 7 bits sequence per ms
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS); // 31
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS);   // 127

    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;                  // 7
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS; // 12

    private final long workerId;
    private long lastTimestamp = -1L;
    private long sequence = 0L;

    public SafeJsIdentifierGenerator() {
        this.workerId = resolveWorkerId();
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalStateException("workerId out of range: " + workerId);
        }
        log.info("SafeJsIdentifierGenerator initialized with workerId={}", workerId);
    }

    @Override
    public Number nextId(Object entity) {
        long id = nextIdInternal();
        if (id > JS_SAFE_MAX) {
            throw new IllegalStateException("Generated id exceeds JS safe max: " + id);
        }
        if (log.isDebugEnabled()) {
            log.debug("Generated JS-safe id: {}", id);
        }
        return id;
    }

    private synchronized long nextIdInternal() {
        long current = currentTime();
        if (current < lastTimestamp) {
            // 时钟回拨，等待至 lastTimestamp 或抛异常
            long offset = lastTimestamp - current;
            if (offset > 5) { // 超过 5ms 认为异常
                throw new IllegalStateException("Clock moved backwards by " + offset + "ms");
            }
            // 等待到 lastTimestamp
            while (currentTime() < lastTimestamp) {
                // busy wait
            }
            current = lastTimestamp;
        }
        if (current == lastTimestamp) {
            sequence = (sequence + 1) & MAX_SEQUENCE;
            if (sequence == 0) { // 序列溢出，等下一毫秒
                current = waitNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = current;
        long timeDelta = current - EPOCH; // 41 bits (足够约69年)
        return (timeDelta << TIMESTAMP_SHIFT) | (workerId << WORKER_ID_SHIFT) | sequence;
    }

    private long waitNextMillis(long lastTs) {
        long ts = currentTime();
        while (ts <= lastTs) {
            ts = currentTime();
        }
        return ts;
    }

    private long currentTime() {
        return System.currentTimeMillis();
    }

    private long resolveWorkerId() {
        // 优先环境变量 HM_WORKER_ID
        String env = System.getenv("HM_WORKER_ID");
        if (env != null) {
            try {
                return Long.parseLong(env) & MAX_WORKER_ID;
            } catch (NumberFormatException ignored) {
                log.warn("Invalid HM_WORKER_ID env: {}", env);
            }
        }
        // 其次根据主机名/IP 最后一个段
        try {
            String host = InetAddress.getLocalHost().getHostAddress();
            String[] parts = host.split("\\.");
            String last = parts[parts.length - 1];
            return Long.parseLong(last) & MAX_WORKER_ID;
        } catch (UnknownHostException | NumberFormatException e) {
            log.warn("Cannot resolve host address, fallback workerId=0: {}", e.getMessage());
            return 0L;
        }
    }
}
